////////////////////////////////////////////////////////////////////////////////
// MKV DEMUXER
////////////////////////////////////////////////////////////////////////////////

#include "session.h"
#include <asm/delay.h>
#include <uapi/time.h>
#include <uapi/stdlib.h>
#include <uapi/string.h>
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include <uapi/bits/byteswap.h>
#include "ff/ff.h"
#else
#include <mve/cv/cv_global.h>
#include "ff.h"
#endif
///////////////////////////////////// define ///////////////////////////////////
#define BLOCK	CV_BITBUFS_SIZE
#define name2str(name) (#name)
#define AVDATA_MAX		(256 * 1024)
#define BUFFER_MAX		((AVDATA_MAX + 4) * 2)
#define MIN_HEADER_LEN	(4 + 4)

/*!
 * MKV element IDs, max 32 bits
 */
/* id of MKV ebml */
#define EBML_ID_HEADER  0x1A45DFA3
/* toplevel segment */
#define MKV_ID_SEGMENT  0x18538067

/* MKV top-level master IDs */
#define MKV_ID_SEEKHEAD      0x114D9B74
/* IDs in the seekhead master */
#define MKV_ID_SEEKENTRY 0x4DBB
/* IDs in the seekpoint master */
#define MKV_ID_SEEKID       0x53AB
#define MKV_ID_SEEKPOSITION 0x53AC // position with seekhead

#define MKV_ID_INFO 0x1549A966
/* IDs in the info master */
#define MKV_ID_TIMECODESCALE 0x2AD7B1 // unit: ns
#define MKV_ID_DURATION      0x4489

#define MKV_ID_TRACKS 0x1654AE6B
/* ID in the tracks master */
#define MKV_ID_TRACKENTRY      0xAE
/* IDs in the trackentry master */
#define MKV_ID_TRACKNUMBER 0xD7
#define MKV_ID_TRACKTYPE   0x83
#define MKV_ID_CODECID     0x86 // the stream type
#define MKV_ID_CODECPRIVATE 0x63A2
#define MKV_ID_TRACKDEFAULTDURATION 0x23E383
#define MKV_ID_TRACKVIDEO   0xE0
/* IDs in the trackvideo master */
#define MKV_ID_VIDEODISPLAYWIDTH  0x54B0
#define MKV_ID_VIDEODISPLAYHEIGHT 0x54BA
#define MKV_ID_VIDEOPIXELWIDTH    0xB0
#define MKV_ID_VIDEOPIXELHEIGHT   0xBA
#define MKV_ID_TRACKAUDIO   0xE1
/* IDs in the trackaudio master */
#define MKV_ID_AUDIOSAMPLINGFREQ 0xB5
#define MKV_ID_AUDIOBITDEPTH 0x6264
#define MKV_ID_AUDIOCHANNELS 0x9F

#define MKV_ID_CLUSTER 0x1F43B675
/* IDs in the cluster master */
#define MKV_ID_CLUSTERTIMECODE 0xE7
#define MKV_ID_SIMPLEBLOCK 0xA3
#define MKV_ID_BLOCKGROUP  0xA0
/* IDs in the blockgroup master */
#define MKV_ID_BLOCK   0xA1

#define MKV_ID_CUES 0x1C53BB6B
/* ID in the cues master */
#define MKV_ID_POINTENTRY  0xBB
/* IDs in the pointentry master */
#define MKV_ID_CUETIME          0xB3
#define MKV_ID_CUETRACKPOSITION 0xB7
/* IDs in the cuetrackposition master */
#define MKV_ID_CUETRACK     0xF7
#define MKV_ID_CUECLUSTERPOSITION 0xF1

///////////////////////////////////// struct ///////////////////////////////////
typedef enum EbmlType {
	EBML_NONE,
	EBML_BIN,
	EBML_STRING,
	EBML_NEST,
	EBML_SKIP,
	EBML_STOP
} EbmlType;

typedef struct {
	uint32_t cur_cluster_addr;	// 0 if all cluster was read, otherwise current cluster address
	uint32_t cur_cluster_len;
	uint32_t cur_cluster_time;	// unit: ms
	uint32_t cur_block_addr;	// block addr in file
	uint32_t cur_block_len;
	uint32_t cur_block_time;	// unit: ms
	unsigned char total_lace_num;
	unsigned char cur_lace_num;
	uint32_t cur_lace_size[32]; //FIXME: use size[sizeof(char)]
} cluster_t;

typedef struct {
	const char *name;	// stream name
	int track_type; 	// 1: video, 2: audio
	int track_number;
	int stream_type;
	int metadata_len;
	char metadata[1024];	// pps/sps/vps info get from MKV_ID_CODECPRIVATE elem
	cluster_t cluster;	// packet address
	int width;
	int height;
	int v_pkt_count;
	int a_pkt_count;
	uint32_t time_scale;
	uint32_t file_duration; // umit: s
	uint32_t pkt_duration;	// unit: ns
	int channels;
	int frequency;
	int bit_depth;
	uint8_t *bufaddr;	// file data cache
	uint32_t bufpos;	// the buffer start position in file
	uint32_t curpos;	// the current read position in buffer
} mkv_track_t;

typedef struct {
	struct SESSION *session;
	struct SESSION *session_video, *session_audio;

	uint32_t tv_valid_size, ta_valid_size;
	long load_size;		// file length
	long load_offset;
	int eof;
	psysbuf_t load_buf, loadbuf_video, loadbuf_audio;
	FIL file;

	int current_track_index; // start from 0
	mkv_track_t video_stream;
	mkv_track_t audio_stream;
	mkv_track_t *current_stream;
	int (*video_write_packet)(void *, media_bsf_opt_t, void *, phys_addr_t, int32_t *, phys_addr_t, int32_t);
	int (*audio_write_packet)(void *, media_bsf_opt_t, void *, phys_addr_t, int32_t *, phys_addr_t, int32_t);
	int state; // 0: get format info, 1: read stream data
	int cur_track_num;
	int mkv_block_len_flag;
#ifdef MEDIA_AV_SYNC
	session_av_sync avsync;
	session_av_sync *sync;
#endif
	uint8_t *readbuf;
} session_unit_t;

typedef struct MkvSyntax {
	unsigned int id;
	char *name;
	EbmlType type;
	int (*parser)(session_unit_t *, struct MkvSyntax *);
	unsigned long size;
	unsigned long offset;
} MkvSyntax;

enum {
	READ_FILE,
	READ_BUFFER
};

//////////////////////////////////// variable //////////////////////////////////
const static unsigned char log2_tab[256] = {
	0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
	5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
	6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
	6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

//////////////////////////////////// function //////////////////////////////////
/*!
 * \brief   mkv file format probe
 *
 * \param[in] punit pointer to file buffer
 * \return    0 if succeeded otherwise failed
 */
static
int mkv_probe(session_unit_t *punit)
{
	unsigned int id;
	psysbuf_t load_buf;
	char probe_data[16];

	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	if(punit->load_size <= 0)
		return -1;

	f_read(&punit->file, (void*)HWADDR(load_buf->haddr), sizeof(probe_data), NULL);
	load_buf->size = sizeof(probe_data);

	/** read EBML header */
	memcpy(&id, (void*)HWADDR(load_buf->haddr), sizeof(id));

	/* match EBML Header id */
	if (id != uswap_32(EBML_ID_HEADER))
		return -EPROTONOSUPPORT;

	return 0;
}

static
int mkv_read_timecodescale(session_unit_t *punit, MkvSyntax *syntax)
{
	unsigned int tmp32 = 0, time_scale = 0;
	int i;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	for (i = 0; i < syntax->size; i++) {
		time_scale <<= 8;
		f_read(&punit->file, &tmp32, 1, NULL);
		time_scale |= tmp32;
	}
	//debug("time_scale: %d\n", time_scale); // units: ns
	punit->video_stream.time_scale = time_scale;

	return 0;
}

static
int mkv_read_tracknumber(session_unit_t *punit, MkvSyntax *syntax)
{
	uint32_t tmp32 = 0;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	f_read(&punit->file, &tmp32, syntax->size, NULL);
	punit->cur_track_num = tmp32;
	debug("\ntrack number: %d, ", punit->cur_track_num);

	return 0;
}

/*!
 * \brief read duration element, get file total time
 */
static
int mkv_read_duration(session_unit_t *punit, MkvSyntax *syntax)
{
	uint64_t tmp64 = 0;
	uint32_t tmp32 = 0;
	uint64_t double_duration = 0;
	uint32_t float_duration = 0;
	double d_duration;
	float f_duration;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	if (syntax->size > 4) {
		f_read(&punit->file, &tmp64, syntax->size, NULL);
		double_duration = uswap_64(tmp64);
		d_duration = *(double *)&double_duration;
		punit->video_stream.file_duration = (uint32_t)(d_duration / 1000 * punit->video_stream.time_scale / 1000000);
	} else {
		f_read(&punit->file, &tmp32, syntax->size, NULL);
		float_duration = uswap_32(tmp32);
		f_duration = *(float *)&float_duration;
		punit->video_stream.file_duration = (uint32_t)(f_duration / 1000 * punit->video_stream.time_scale / 1000000);
	}
	debug("file duration: %ds\n", punit->video_stream.file_duration);

	return 0;
}

/*!
 * \brief read trak element
 */
static
int mkv_read_codecid(session_unit_t *punit, MkvSyntax *syntax)
{
	mkv_track_t *stream = NULL;
	char str[32];

	punit->current_track_index++;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	memset(str, 0, sizeof(str));

	f_read(&punit->file, str, syntax->size, NULL);
	str[syntax->size] = '\0';
	str[sizeof(str) - 1] = '\0';

	debug("codec ID: %s, ", str);
	if (strstr(str, "AVC")) { // V_MPEG4/ISO/AVC
		stream = &punit->video_stream;
		punit->current_stream = stream;
		stream->track_type = 1; //FIXME: get this in embl TrackType(cmd 0x83)
		stream->name = "h264";
		stream->stream_type = SST_H264;
	} else if (strstr(str, "HEVC")) { // V_MPEGH/ISO/HEVC
		stream = &punit->video_stream;
		punit->current_stream = stream;
		stream->track_type = 1;
		stream->name = "hevc";
		stream->stream_type = SST_HEVC;
	} else if (strstr(str, "V_MPEG2")) { // V_MPEG2
		stream = &punit->video_stream;
		punit->current_stream = stream;
		stream->track_type = 1;
		stream->name = "mpeg2";
		stream->stream_type = SST_MPG2;
	} else if (strstr(str, "SP")) { // V_MPEG4/ISO/ASP or SP
		stream = &punit->video_stream;
		punit->current_stream = stream;
		stream->track_type = 1;
		stream->name = "mpeg4";
		stream->stream_type = SST_MPG4;
	} else if (strstr(str, "AAC")) { // A_AAC
		stream = &punit->audio_stream;
		punit->current_stream = stream;
		stream->track_type = 2;
		stream->name = "aac";
		stream->stream_type = SST_AAC;
	} else if (strstr(str, "L3")) { // A_MPEG/L3
		stream = &punit->audio_stream;
		punit->current_stream = stream;
		stream->track_type = 2;
		stream->name = "MP3";
		stream->stream_type = SST_MP3;
	} else {
		debug("%s is not surppot!\n", str);
		stream = &punit->audio_stream;
		punit->current_stream = stream;
		stream->track_type = 2;
		stream->name = "aac";
	}

	stream->track_number = punit->cur_track_num;
	if (stream->name)
		debug("stream name: %s\n", stream->name);

	return 0;
}

static
int mkv_read_defaultduration(session_unit_t *punit, MkvSyntax *syntax)
{
	uint8_t tmp8 = 0;
	uint32_t pkt_duration = 0;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	while (syntax->size) {
		f_read(&punit->file, &tmp8, 1, NULL);
		pkt_duration = (pkt_duration << 8) | tmp8;
		syntax->size--;
	}

	punit->current_stream->pkt_duration = pkt_duration;
	debug("packet duration: %dus\n", pkt_duration / 1000);

	return 0;
}

static
int mkv_read_pixelwidth(session_unit_t *punit, MkvSyntax *syntax)
{
	unsigned short tmp16, width;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	f_read(&punit->file, &tmp16, syntax->size, NULL);
	width = uswap_16(tmp16);
	debug("width: %d\n", width);
	punit->current_stream->width = width;

	return 0;
}

static
int mkv_read_pixelheight(session_unit_t *punit, MkvSyntax *syntax)
{
	unsigned short tmp16, height;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	f_read(&punit->file, &tmp16, syntax->size, NULL);
	height = uswap_16(tmp16);
	debug("height: %d\n", height);
	punit->current_stream->height = height;

	return 0;
}

static
int mkv_read_channels(session_unit_t *punit, MkvSyntax *syntax)
{
	unsigned short channels = 0;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	f_read(&punit->file, &channels, syntax->size, NULL); // 1 byte
	debug("channels: %d\n", channels);
	punit->current_stream->channels = channels;

	return 0;
}

static
int mkv_read_frequency(session_unit_t *punit, MkvSyntax *syntax)
{
	uint64_t tmp64 = 0;
	uint32_t tmp32 = 0;
	uint64_t double_frequency = 0;
	uint32_t float_frequency = 0;
	double d_frequency = 0;
	float f_frequency = 0;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	if (syntax->size > 4) {
		f_read(&punit->file, &tmp64, syntax->size, NULL);
		double_frequency = uswap_64(tmp64);
		d_frequency = *(double *)&double_frequency;
		punit->audio_stream.frequency = (uint32_t)d_frequency;
	} else {
		f_read(&punit->file, &tmp32, syntax->size, NULL);
		float_frequency = uswap_32(tmp32);
		f_frequency = *(float *)&float_frequency;
		punit->audio_stream.frequency = (uint32_t)f_frequency;
	}
	debug("frequency: %dHz\n", punit->audio_stream.frequency);

	return 0;
}

static
int mkv_read_bitdepth(session_unit_t *punit, MkvSyntax *syntax)
{
	unsigned short bit_depth = 0;

	if (punit->current_track_index > 2) {
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + syntax->size);
		return 0;
	}

	f_read(&punit->file, &bit_depth, syntax->size, NULL); // 1 byte
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	if (bit_depth > 16)
		bit_depth /= 2; //!!!: temporary fix the 32bit decode error bug
#endif
	debug("bit_depth: %d\n", bit_depth);
	punit->current_stream->bit_depth = bit_depth;

	return 0;
}

/*!
 * \brief get pps data
 * @see http://matroska.sourceforge.net/technical/specs/index.html
 * @see https://forum.doom9.org/showthread.php?t=181316
 */
static
int mkv_read_private(session_unit_t *punit, MkvSyntax *syntax)
{
	mkv_track_t *stream = NULL;
	int total_len = syntax->size;
	int cur_len = 0;
	unsigned char tag_size;
	unsigned int start_code4 = 0x01000000; /**< start code of h264 NALU */
	char start_code3[3] = {0x00, 0x00, 0x01};

	if (punit->current_track_index > 2)
		goto exit;

	if (syntax->size < 25) //FIXME:
		goto exit;

	stream = punit->current_stream;
	stream->metadata_len = 0;

	if (stream->stream_type == SST_MPG4) {
		f_read(&punit->file, stream->metadata + stream->metadata_len, total_len, NULL);
		cur_len += total_len;
		stream->metadata_len += total_len;
	} else if (stream->stream_type == SST_H264) {
		/* skip 5 bytes */
		f_lseek(&punit->file, f_tell(&punit->file) + 5); cur_len += 5;
		/* skip 2 bytes */
		f_lseek(&punit->file, f_tell(&punit->file) + 2); cur_len += 2;
		/* get sps len */
		f_read(&punit->file, &tag_size, 1, NULL); cur_len += 1;

		//debug("1 sps len: %d\n", tag_size);
		memcpy(stream->metadata, &start_code3, 3); stream->metadata_len += 3;
		f_read(&punit->file, stream->metadata + stream->metadata_len, tag_size, NULL);
		cur_len += tag_size;
		stream->metadata_len += tag_size;

		/* skip 2 bytes */
		f_lseek(&punit->file, f_tell(&punit->file) + 2); cur_len += 2;
		/* get pps len */
		f_read(&punit->file, &tag_size, 1, NULL); cur_len += 1;
		//debug("2 pps len: %d\n", tag_size);
		memcpy(stream->metadata + stream->metadata_len, &start_code4, 4);
		stream->metadata_len += 4;
		f_read(&punit->file, stream->metadata + stream->metadata_len, tag_size, NULL);
		cur_len += tag_size;
		stream->metadata_len += tag_size;
		//TODO: get fps
	} else { // hevc
	//aligned(8) class HEVCDecoderConfigurationRecord {
		//  unsigned int(8) configurationVersion = 1;
		//  unsigned int(2) general_profile_space;
		//  unsigned int(1) general_tier_flag;
		//  unsigned int(5) general_profile_idc;
		//  unsigned int(32) general_profile_compatibility_flags;
		//  unsigned int(48) general_constraint_indicator_flags;
		//  unsigned int(8) general_level_idc;
		//  bit(4) reserved = ‘1111’b;
		//  unsigned int(12) min_spatial_segmentation_idc;
		//  bit(6) reserved = ‘111111’b;
		//  unsigned int(2) parallelismType;
		//  bit(6) reserved = ‘111111’b;
		//  unsigned int(2) chroma_format_idc;
		//  bit(5) reserved = ‘11111’b;
		//  unsigned int(3) bit_depth_luma_minus8;
		//  bit(5) reserved = ‘11111’b;
		//  unsigned int(3) bit_depth_chroma_minus8;
		//  bit(16) avgFrameRate;
		//  bit(2) constantFrameRate;
		//  bit(3) numTemporalLayers;
		//  bit(1) temporalIdNested;
		//  unsigned int(2) lengthSizeMinusOne;
		f_lseek(&punit->file, f_tell(&punit->file) + 22); cur_len += 22;

		//  unsigned int(8) numOfArrays;
		uint8_t array_num;
		f_read(&punit->file, &array_num, 1, NULL); cur_len += 1;
		//debug("numOfArrays:%x\n",array_num);

		//  for (j=0; j < numOfArrays; j++) {
		//    bit(1) array_completeness;
		//    unsigned int(1) reserved = 0;
		//    unsigned int(6) NAL_unit_type;
		//    unsigned int(16) numNalus;
		//    for (i=0; i< numNalus; i++) {
		//      unsigned int(16) nalUnitLength;
		//      bit(8*nalUnitLength) nalUnit;
		//    }
		//  }
		//}
		int i, j;
		uint8_t nal_type;
		uint16_t tmp16, nalu_num, nal_size;
		for (i = 0; i < array_num; i++) {
			f_read(&punit->file, &nal_type, 1, NULL); cur_len += 1;
			nal_type &= 0x3F;
			//debug("\nnal_type:%x\n",nal_type);

			f_read(&punit->file, &tmp16, 2, NULL); cur_len += 2;
			nalu_num = uswap_16(tmp16);
			//debug("nalu_num:%x\n",nalu_num);

			for (j = 0; j < nalu_num; j++) {
				f_read(&punit->file, &tmp16, 2, NULL); cur_len += 2;
				nal_size = uswap_16(tmp16);
				//debug("nal_size:%x\n",nal_size);
				if (nal_type == 0x20 || nal_type == 0x21 || nal_type == 0x22) {
					memcpy(stream->metadata + stream->metadata_len, &start_code4, 4);
					stream->metadata_len += 4;
					f_read(&punit->file, stream->metadata + stream->metadata_len, nal_size, NULL); cur_len += nal_size;
					stream->metadata_len += nal_size;
				} else { // nal_type == 0x27
					f_lseek(&punit->file, f_tell(&punit->file) + nal_size); cur_len += nal_size;
				}
			}
		}
	}

exit:
	/* skip the bytes remaining */
	f_lseek(&punit->file, f_tell(&punit->file) + total_len - cur_len); cur_len += 4;

	return 0;
}

/*!
 * \brief get first cluster address
 */
static
int mkv_read_cluster(session_unit_t *punit, MkvSyntax *syntax)
{
	mkv_track_t *astream = &punit->audio_stream;
	mkv_track_t *vstream = &punit->video_stream;

	astream->cluster.cur_cluster_addr = f_tell(&punit->file);
	vstream->cluster.cur_cluster_addr = f_tell(&punit->file);
	astream->cluster.cur_cluster_len =  syntax->size;
	vstream->cluster.cur_cluster_len =  syntax->size;

	unsigned int addr = f_tell(&punit->file);
	int remainder = addr % 4;
	addr -= remainder;
	astream->bufpos = addr;
	vstream->bufpos = addr;
	astream->curpos = remainder;
	vstream->curpos = remainder;

	f_lseek(&punit->file, addr);
	f_read(&punit->file, astream->bufaddr, AVDATA_MAX, NULL);
	memcpy(vstream->bufaddr, astream->bufaddr, AVDATA_MAX);

	return -1; // exit file read
}

/*!
 * \brief get real data from variable length data
 */
static
int ebml_read_num(session_unit_t *punit, int max_size, unsigned long *number, int type)
{
	unsigned long total = 0;
	int read, n = 1;
	unsigned char tmp;

	/* The first byte tells us the length in bytes - except when it is zero. */
	if (type == READ_FILE)
		f_read(&punit->file, &total, 1, NULL);
	else {
		memcpy(&total, punit->current_stream->bufaddr + punit->current_stream->curpos, 1);
		punit->current_stream->curpos++;
	}
	/* get the length of the EBML number */
	read = 8 - log2_tab[total];

	/* read out length */
	total ^= 1 << log2_tab[total];
	while (n++ < read) {
		if (type == READ_FILE)
			f_read(&punit->file, &tmp, 1, NULL);
		else {
			memcpy(&tmp, punit->current_stream->bufaddr + punit->current_stream->curpos, 1);
			punit->current_stream->curpos++;
		}
		total = (total << 8) | tmp;
	}

	*number = total;

	return read;
}

/*!
 *\brief get embl id
 */
static
MkvSyntax *ebml_parse_id(MkvSyntax *syntax, unsigned int id)
{
	static MkvSyntax null_syntax = {0, NULL, EBML_NONE, 0, 0};
	int i;

	for (i = 0; syntax[i].id; i++) {
		if (id == syntax[i].id) {
			return &syntax[i];
		}
	}

	return &null_syntax;
}

/*!
 * \brief read parent ebml and child ebml with recursion
 */
static
int ebml_parse(session_unit_t *punit, MkvSyntax *syntax)
{
	static int count_test = 0;

	int res;
	int ret = 0;
	unsigned long id;
	unsigned int current_id;
	unsigned long length;
	MkvSyntax *id_syntax;

	long seek_start;

	seek_start = f_tell(&punit->file);

	/** get id */
	res = ebml_read_num(punit, 4, &id, READ_FILE);
	current_id = id | 1 << 7 * res;

	/** get option of the id */
	id_syntax = ebml_parse_id(syntax, current_id);

	if (id_syntax->id == EBML_ID_HEADER)
		count_test = 0;
	else
		count_test++;

	/** get length */
	res = ebml_read_num(punit, 8, &length, READ_FILE);
	if (id_syntax->name) {
		//debug("id: <{%s}> (0x%x), len: %ld, i: %d, addr:%x, type:%d, func:%p\n",
		//		id_syntax->name, id_syntax->id, length, count_test,
		//		f_tell(&punit->file), id_syntax->type, id_syntax->parser);
	} else {
		//debug("Ignore and skip id: 0x%x, len: 0x%lx, addr: %x\n", current_id, length, f_tell(&punit->file));
	}

	if (id_syntax->id) { // the first sub elem
		id_syntax->size = length;
		id_syntax->offset = f_tell(&punit->file);
	}

	switch (id_syntax->type) {
	case EBML_BIN:
	case EBML_STRING:
		/* read bin data in parser function */
		if (id_syntax->parser)
			id_syntax->parser(punit, id_syntax);
		else
			f_lseek(&punit->file, f_tell(&punit->file) + length);
		break;
	case EBML_NONE:
	case EBML_SKIP:
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + length);
		break;
	case EBML_NEST:
		/** recursive call */
		if (id_syntax->parser) {
			res = id_syntax->parser(punit, id_syntax);
		} else {
			res = ebml_parse(punit, syntax);
		}

		if (res)
			return res;
		break;
	default:
		/* skip the data */
		f_lseek(&punit->file, f_tell(&punit->file) + length);
		break;
	}

	if (id_syntax->id == MKV_ID_CLUSTER)
		ret = -1; // exit the file info get when the cluster is read

	return ret;
}

MkvSyntax mkv_syntax_preview [] = {
	{EBML_ID_HEADER, name2str(EBML_ID_HEADER), EBML_SKIP},
	{MKV_ID_SEGMENT, name2str(MKV_ID_SEGMENT), EBML_NEST},

	//TODO: add seek function
	//{MKV_ID_SEEKHEAD, name2str(MKV_ID_SEEKHEAD), EBML_NEST},
	//{MKV_ID_SEEKENTRY, name2str(MKV_ID_SEEKENTRY), EBML_NEST},
	//{MKV_ID_SEEKID, name2str(MKV_ID_SEEKID), EBML_STRING},
	//{MKV_ID_SEEKPOSITION, name2str(MKV_ID_SEEKPOSITION), EBML_BIN},

	{MKV_ID_INFO, name2str(MKV_ID_INFO), EBML_NEST},
	{MKV_ID_TIMECODESCALE, name2str(MKV_ID_TIMECODESCALE), EBML_BIN, mkv_read_timecodescale, 0, 0},
	{MKV_ID_DURATION, name2str(MKV_ID_DURATION), EBML_BIN, mkv_read_duration, 0, 0},

	{MKV_ID_TRACKS, name2str(MKV_ID_TRACKS), EBML_NEST},
	{MKV_ID_TRACKENTRY, name2str(MKV_ID_TRACKENTRY), EBML_NEST}, // alloc the current stream
	{MKV_ID_TRACKNUMBER, name2str(MKV_ID_TRACKNUMBER), EBML_BIN, mkv_read_tracknumber, 0, 0},
	{MKV_ID_TRACKTYPE, name2str(MKV_ID_TRACKTYPE), EBML_BIN},
	{MKV_ID_CODECID, name2str(MKV_ID_CODECID), EBML_BIN, mkv_read_codecid, 0, 0},
	{MKV_ID_TRACKDEFAULTDURATION, name2str(MKV_ID_TRACKDEFAULTDURATION), EBML_BIN, mkv_read_defaultduration, 0, 0},
	//TODO: get fps
	{MKV_ID_CODECPRIVATE, name2str(MKV_ID_CODECPRIVATE), EBML_BIN, mkv_read_private, 0, 0},
	{MKV_ID_TRACKVIDEO, name2str(MKV_ID_TRACKVIDEO), EBML_NEST},
	{MKV_ID_VIDEODISPLAYWIDTH, name2str(MKV_ID_VIDEODISPLAYWIDTH), EBML_BIN},
	{MKV_ID_VIDEODISPLAYHEIGHT, name2str(MKV_ID_VIDEODISPLAYHEIGHT), EBML_BIN},
	{MKV_ID_VIDEOPIXELWIDTH, name2str(MKV_ID_VIDEOPIXELWIDTH), EBML_BIN, mkv_read_pixelwidth, 0, 0},
	{MKV_ID_VIDEOPIXELHEIGHT, name2str(MKV_ID_VIDEOPIXELHEIGHT), EBML_BIN, mkv_read_pixelheight, 0, 0},
	{MKV_ID_TRACKAUDIO, name2str(MKV_ID_TRACKAUDIO), EBML_NEST},
	{MKV_ID_AUDIOCHANNELS, name2str(MKV_ID_AUDIOCHANNELS), EBML_BIN, mkv_read_channels, 0, 0},
	{MKV_ID_AUDIOSAMPLINGFREQ, name2str(MKV_ID_AUDIOSAMPLINGFREQ), EBML_BIN, mkv_read_frequency, 0, 0},
	{MKV_ID_AUDIOBITDEPTH, name2str(MKV_ID_AUDIOBITDEPTH), EBML_BIN, mkv_read_bitdepth, 0, 0},

	//TODO: add seek function
	//{MKV_ID_CUES, name2str(MKV_ID_CUES), EBML_NEST},
	//{MKV_ID_POINTENTRY, name2str(MKV_ID_POINTENTRY), EBML_NEST},
	//{MKV_ID_CUETIME, name2str(MKV_ID_CUETIME), EBML_BIN},
	//{MKV_ID_CUETRACKPOSITION, name2str(MKV_ID_CUETRACKPOSITION), EBML_NEST},
	//{MKV_ID_CUETRACK, name2str(MKV_ID_CUETRACK), EBML_SKIP},
	//{MKV_ID_CUECLUSTERPOSITION, name2str(MKV_ID_CUECLUSTERPOSITION), EBML_SKIP},

	{MKV_ID_CLUSTER, name2str(MKV_ID_CLUSTER), EBML_NEST, mkv_read_cluster, 0, 0},
	{ 0 }
};

MkvSyntax mkv_syntax_packet [] = {
	{MKV_ID_CLUSTER, name2str(MKV_ID_CLUSTER), EBML_NEST},
	{MKV_ID_CLUSTERTIMECODE, name2str(MKV_ID_CLUSTERTIMECODE), EBML_BIN},
	{MKV_ID_SIMPLEBLOCK, name2str(MKV_ID_SIMPLEBLOCK), EBML_BIN},
	{MKV_ID_BLOCK, name2str(MKV_ID_BLOCK), EBML_BIN},
	{MKV_ID_BLOCKGROUP, name2str(MKV_ID_BLOCKGROUP), EBML_BIN},
	{ 0 }
};

/*!
 * \brief     get stream information
 *
 * \param[in] punit pointer to file buffer
 * \return    0 if succeeded otherwise failed
 */
static
int mkv_streaminfo_get(session_unit_t *punit)
{
	int ret = 0;
	long file_len;

	file_len = punit->load_size;
	f_lseek(&punit->file, 0); // rewind to origin
	punit->current_track_index = 0;

	/* read all EBML elements. */
	do {
		ret = ebml_parse(punit, mkv_syntax_preview);
	} while (!ret && f_tell(&punit->file) < file_len);

	return 0;
}

/*!
 * get cluster time code
 */
static
int get_timecode(session_unit_t *punit, mkv_track_t *track, int length)
{
	uint32_t timecode = 0;
	unsigned char tmp;

	//modify to ebml_read_num()
	while (length) {
		//f_read(&punit->file, &tmp, 1, NULL);
		memcpy(&tmp, punit->current_stream->bufaddr + punit->current_stream->curpos, 1);
		punit->current_stream->curpos++;
		timecode = (timecode << 8) | tmp;
		length--;
	}

	track->cluster.cur_cluster_time = timecode;

	return 0;
}

static
int get_cluster_info(session_unit_t *punit, mkv_track_t *track, unsigned int length)
{
	track->cluster.cur_cluster_addr = punit->current_stream->bufpos
			+ punit->current_stream->curpos;
	track->cluster.cur_cluster_len = length;

	return 0;
}

/*!
 * parse the lacing(frame) of mkv block(packet)
 */
static
int mkv_parse_lace(session_unit_t *punit, mkv_track_t *track, unsigned char type, int size)
{
	unsigned char laces; // num of laces
	int n;
	uint32_t *lace_size = track->cluster.cur_lace_size;

	if (!type)
		return size;

	if (size < 0)
		return size;

	memcpy(&laces, punit->current_stream->bufaddr + punit->current_stream->curpos, sizeof(char));
	punit->current_stream->curpos += sizeof(char);

	laces += 1;
	size -= 1;

	switch (type) {
	case 0x1: /* Xiph lacing */
	{
		uint8_t temp;
		uint32_t total = 0;
		for (n = 0; n < laces - 1; n++) {
			lace_size[n] = 0;

			do {
				if (size <= total)
					return -1;
				memcpy(&temp, punit->current_stream->bufaddr + punit->current_stream->curpos, sizeof(char));
				punit->current_stream->curpos += sizeof(char);
				total        += temp;
				lace_size[n] += temp;
				size         -= 1;
			} while (temp == 0xff);
			//debug("Xiph lases_size[%d]: %d\n", n, lace_size[n]);
		}
		if (size < total)
			return -1;

		lace_size[n] = size - total;
		//debug("Xiph lases_size[%d]: %d\n", n, lace_size[n]);
		break;
	}

	case 0x2: /* fixed-size lacing */
		if (size % laces)
			return -1;
		for (n = 0; n < laces; n++) {
			lace_size[n] = size / laces;
			//debug("fixed-size lases_size[%d]: %d\n", n, lace_size[n]);
		}
		break;

	case 0x3: /* EBML lacing */
	{
		unsigned long num;
		uint64_t total;
		int offset;

		n = ebml_read_num(punit, 8, &num, READ_BUFFER);
		if (n < 0)
			return size;
		total = lace_size[0] = num; // size

		offset = n;
		for (n = 1; n < laces - 1; n++) {
			unsigned long snum;
			int r;
			r = ebml_read_num(punit, 8, &snum, READ_BUFFER);
			/* make signed (weird way) */
			snum = snum - ((1LL << (7 * r - 1)) - 1);
			if (r < 0)
				return size;

			lace_size[n] = lace_size[n - 1] + snum;
			total       += lace_size[n];
			offset      += r;
			//debug("EBML lacing lases_size[%d]: %d\n", n, lace_size[n]);
		}
		size -= offset;
		if (size < total)
			return size;

		lace_size[laces - 1] = size - total;
		break;
	}
	}

	track->cluster.cur_block_addr =  punit->current_stream->bufpos + punit->current_stream->curpos;
	track->cluster.total_lace_num = laces;
	track->cluster.cur_lace_num = 1;
	track->cluster.cur_block_len = track->cluster.cur_lace_size[track->cluster.cur_lace_num - 1];
	track->cluster.cur_lace_num++;

	return size;
}

/*!
 * get next block addr, length and time
 *
 * {|Track Number|Timecode|Flags<Keyframe|rsv |Invisible|Lacing|Discardable>
 * {|1           |2       |1    <1'b     |3'b0|1'b      |2'b   |1'b        > *  |((if <Lacing 2b'11>)more...)|}
 * @see https://blog.csdn.net/zhangrui_fslib_org/article/details/50758837
 * @see https://www.cnblogs.com/shakin/p/4465580.html
 * @see https://blog.csdn.net/tx3344/article/details/8203260
 * @see matroska_parse_laces() of ffmpeg/libavformat/matroskadec.c
 *
 * \return the current block length
 */
static
int get_block_info(session_unit_t *punit, mkv_track_t *track, unsigned int length)
{
	int ret = 0;
	unsigned char header_track;
	unsigned short timecode;
	unsigned char flag;
	unsigned char lacing;
	unsigned int block_header; // track_id + timecode + flags

	unsigned long cur_track_num = 0;
	ebml_read_num(punit, 4, &cur_track_num, READ_BUFFER);

	timecode = 0;
	memcpy(&timecode, punit->current_stream->bufaddr + punit->current_stream->curpos, sizeof(char));
	punit->current_stream->curpos += sizeof(char);
	timecode <<= 8;
	memcpy(&timecode, punit->current_stream->bufaddr + punit->current_stream->curpos, sizeof(char));
	punit->current_stream->curpos += sizeof(char);

	memcpy(&flag, punit->current_stream->bufaddr + punit->current_stream->curpos, sizeof(flag));
	punit->current_stream->curpos += sizeof(flag);

	/**
	 * 00 : no lacing
	 * 01 : Xiph lacing {|laces num - 1|lace_size[0~num]|lace_data[lace_size0~lace_size-num]|}
	 *                  (lace_size is 1byte 1~0xFE, or nbyte 0xFF+...+0xFF+1~0xFe)
	 * 10 : fixed-size lacing {|laces num - 1|laces data|} (all lace size is total_size/lace_num)
	 * 11 : EBML lacing {laces num is EBML type: |laces num - 1|nB EBML size|data}
	 */
	lacing = (flag & 0x06) >> 1; // bit 1 and 2

	if (cur_track_num == track->track_number) {
		track->cluster.cur_block_time = timecode;
		if (lacing > 0) {
			ret = mkv_parse_lace(punit, track, lacing, length - sizeof(block_header)); // addr is current
		} else {
			ret = length - sizeof(block_header);
			track->cluster.cur_block_addr = track->bufpos + track->curpos;//f_tell(&punit->file);
			track->cluster.cur_block_len = length - sizeof(block_header);

			uint8_t *tmpdata = (uint8_t *)(punit->current_stream->bufaddr + punit->current_stream->curpos);
			if (tmpdata[sizeof(block_header) + 2] != 0x00) {
				//mkv video block bug, should add 0x00 in byte 6, length add 1.
				//debug("mkv video block bug: %x\n", tmpdata[sizeof(block_header) + 2]);
				punit->mkv_block_len_flag = 1;
			}
		}
	} else {
		ret = 0;
		punit->current_stream->curpos += (length - sizeof(block_header));
	}
	return ret;
}

static int buffer_update(session_unit_t *punit, mkv_track_t *track, int pos)
{
	unsigned int addr = track->bufpos + track->curpos + pos;

	int remainder = addr % 4;
	addr -= remainder;
	track->bufpos = addr;
	track->curpos = remainder;

	f_lseek(&punit->file, addr);
	f_read(&punit->file, track->bufaddr, AVDATA_MAX, NULL);

	return 0;
}

static int cluster_parser(session_unit_t *punit, mkv_track_t *track)
{
	int ret = 0, res;
	int data_count;
	unsigned long id, current_id;
	unsigned long length;
	int pos;
	MkvSyntax *id_syntax;

	if (track->cluster.total_lace_num > 0
			&& track->cluster.cur_lace_num <= track->cluster.total_lace_num) {
		if (track->cluster.cur_lace_num > 1)
			track->cluster.cur_block_addr += track->cluster.cur_lace_size[track->cluster.cur_lace_num - 2];
		track->cluster.cur_block_len = track->cluster.cur_lace_size[track->cluster.cur_lace_num - 1];
		//TODO: add track->cluster.cur_block_time
		track->cluster.cur_lace_num++;
#ifdef MEDIA_AV_SYNC
		punit->sync->lace_total_num++;
#endif
		return track->cluster.cur_block_len;
	} else {
		track->cluster.total_lace_num = 0;
		track->cluster.cur_lace_num = 0;
	}

	do {
		pos = 0;
		data_count = AVDATA_MAX - track->curpos;
		if (data_count < 3) {
			buffer_update(punit, track, pos);
			continue;
		}

		/** get id */
		res = ebml_read_num(punit, 4, &id, READ_BUFFER);
		current_id = id | 1 << 7 * res;
		pos -= res;
		data_count = AVDATA_MAX - track->curpos;
		if (data_count < 2) {
			buffer_update(punit, track, pos);
			continue;
		}

		/** get length */
		res = ebml_read_num(punit, 8, &length, READ_BUFFER);
		pos -= res;
		data_count = AVDATA_MAX - track->curpos;

		/** get option of the id */
		id_syntax = ebml_parse_id(mkv_syntax_packet, current_id);
		//debug("cluster->id: <{%s}> (0x%x), len: 0x%x, type:%d, func:%p\n",
		//	id_syntax->name, current_id, length, id_syntax->type, id_syntax->parser);

		if (id_syntax->type != EBML_NEST && data_count < length) { //NOTE: buffer should be large than movie packet
			if (length > AVDATA_MAX) {
				debug("The mkv block len 0x%x is large than packet buffer len 0x%x, or file end!\n", length, AVDATA_MAX);
				return -1;
			}
			buffer_update(punit, track, pos);
			continue;
		}

		if (current_id == MKV_ID_CLUSTERTIMECODE) {
			get_timecode(punit, track, length);
			continue;
		} else if (current_id == MKV_ID_SIMPLEBLOCK ||
				   current_id == MKV_ID_BLOCK) {
			ret = get_block_info(punit, track, length);
			if (ret)
				break;
		} else if (current_id == MKV_ID_BLOCKGROUP) {
			continue;
		} else if (current_id == MKV_ID_CLUSTER) {
			get_cluster_info(punit, track, length);
			continue;
		} else {
			track->curpos += length;
			continue;
		}
	} while (!ret);

	return ret;
}

/*!
 * read one block or a slice of block, or lacing of block
 */
static
int packet_read(session_unit_t *punit, media_read_opt_t try, uint32_t rlen, uint32_t *pos, mkv_track_t *track)
{
	psysbuf_t load_buf = punit->load_buf;
	int32_t read_size = -1;
	uint32_t cur_pos = *pos;
	uint32_t temp_pos;
	int ret;

	/* param check */
	if (track->cluster.cur_cluster_addr == 0) { // the stream end
		return 0;
	}

	if (track->cluster.cur_block_addr == 0) { // the stream start, get the first block addr and info
		ret = cluster_parser(punit, track); //TODO: record the file end state
		if (ret < 0) {
			return ret;
		}
	}

	if ((try == PREVIEW_FIRST_SLICE) || (try == READ_FIRST_SLICE)
		|| (try == PREVIEW_PACKET)) {
		cur_pos = 0;
		read_size = rlen;
	} else if (try == READ_PACKET) {
		cur_pos = 0;
		read_size = track->cluster.cur_block_len;
	} else // PREVIEW_OTHER_CLICE, READ_OTHER_CLICE, READ_CLICE_PURE
		read_size = rlen;

	//TODO: add the compare of read_size and buffer length
	/* read one packet or a slice of packet */
	temp_pos = track->cluster.cur_block_addr - track->bufpos + cur_pos ;
	track->curpos = temp_pos + read_size;
	memcpy((void *)HWADDR(load_buf->haddr), track->bufaddr + temp_pos, read_size);
	load_buf->size = read_size;

	if (try == PREVIEW_PACKET) {
		read_size = track->cluster.cur_block_len;
	}
	if ((try == PREVIEW_PACKET) || (try == PREVIEW_FIRST_SLICE)
		|| (try == PREVIEW_OTHER_CLICE))
		goto exit;

	if ((try == READ_FIRST_SLICE) || (try == READ_OTHER_CLICE)
		|| (try == READ_CLICE_PURE)) {
		cur_pos += read_size;
		if (cur_pos < track->cluster.cur_block_len)
			goto exit;
	}

	/* set next address */
	ret = cluster_parser(punit, track);
	if (ret < 0) {
		return ret;
	}

exit:
	if ((try == READ_FIRST_SLICE) || (try == READ_OTHER_CLICE)
		|| (try == READ_CLICE_PURE))
		*pos = cur_pos;

	return read_size;
}

static
psysbuf_t alloc_video_buf(session_unit_t *punit)
{
	psysbuf_t buf;

	/* get video send buffer */
	if (punit->session_video == NULL)
		return NULL;
	buf = punit->loadbuf_video;
	if (buf == NULL) {
		buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
		if (buf != NULL)
			punit->loadbuf_video = buf;
		else
			return NULL;
	}
	if (punit->loadbuf_video == NULL)
		return NULL;

	return buf;
}

static
void push_video_buf(session_unit_t *punit, psysbuf_t buf)
{
	if (buf->size > 0) {
#ifdef MEDIA_AV_SYNC
		buf->user.nHighPart = punit->video_stream.v_pkt_count++;
		buf->user.nLowPart = punit->sync->v_cur_pkt_dts;
		buf->offset = 0;
#endif
		punit->loadbuf_video = NULL;
		SessionBufferPush(punit->session_video, buf);
	}
}

static
psysbuf_t alloc_audio_buf(session_unit_t *punit)
{
	psysbuf_t buf;

	/* get audio send buffer */
	if (punit->session_audio == NULL)
		return NULL;
	buf = punit->loadbuf_audio;
	if (buf == NULL) {
		buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (buf != NULL)
			punit->loadbuf_audio = buf;
		else
			return NULL;
	}
	if (punit->loadbuf_audio == NULL)
		return NULL;

	return buf;
}

static
void push_audio_buf(session_unit_t *punit, psysbuf_t buf)
{
	if (buf->size > 0) {
#ifdef MEDIA_AV_SYNC
		buf->flags |= SYS_BUF_FLAG_TOP_PRESENT;
		buf->user.nHighPart = punit->audio_stream.a_pkt_count++;
		buf->user.nLowPart = punit->sync->a_cur_pkt_dts;
		buf->offset = 0;
#endif
		punit->loadbuf_video = NULL;
		punit->loadbuf_audio = NULL;
		SessionBufferPush(punit->session_audio, buf);
	}
}

static
int get_time_diff(session_unit_t *punit)
{
	return 0;
}

/*!
 * \brief get packet data
 */
static
long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	psysbuf_t load_buf, buf;
	int32_t ret = 0;
	cluster_t *vc = &punit->video_stream.cluster;
	cluster_t *ac = &punit->audio_stream.cluster;
	int32_t read_size;
	nalustate_t nalstate;
	uint32_t pos;
	int32_t total_packet_size, cur_packet_size = 0;
	int slice_flag = 0;
	int32_t remain_size;
	int first_enter = 0;
	media_read_opt_t read_preview_opt, read_data_opt;
	media_bsf_opt_t bsf_preview_opt, bsf_data_opt;

	/* get the buffer parse packet of mp4 */
	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}
	if (ac->cur_cluster_addr == 0 && vc->cur_cluster_addr == 0) {
		punit->eof = 1;
		goto exit;
	}

	/* get video packet */
	if (punit->session_video) {
		//DEL: use more buffer list
		if (punit->session_video->state != SSTATE_RUNNING_IDLE) {
			goto audio;
		}
	} else
		goto audio;

	if (vc->cur_cluster_addr == 0)
		goto audio;

	punit->current_stream = &punit->video_stream;
	read_size = packet_read(punit, PREVIEW_PACKET, 8, &pos, &punit->video_stream);
	if (read_size <= 0) { // the stream end
		ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
		punit->session_video = NULL;
		if (punit->session_audio != NULL) {
			ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
			punit->session_audio = NULL;
		}
		goto audio;
	}
	videoconfig_t vcfg;
	vcfg.metadata = punit->video_stream.metadata;
	vcfg.metadata_len = punit->video_stream.metadata_len;
	vcfg.mkv_block_len_flag = punit->mkv_block_len_flag;
#ifdef MEDIA_AV_SYNC
	punit->sync->v_cur_pkt_dts = vc->cur_cluster_time + vc->cur_block_time; // notice: the play end time
	//debug("video dts time:%ums\n", punit->sync->v_cur_pkt_dts);
#endif

	total_packet_size = read_size;
	if (read_size <= BLOCK) {
		read_size = packet_read(punit, PREVIEW_PACKET, read_size, &pos, &punit->video_stream);
		if (read_size <= 0) { // the stream end
			ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
			punit->session_video = NULL;
			if (punit->session_audio != NULL) {
				ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
				punit->session_audio = NULL;
			}
			goto audio;
		}

		nalstate.total_len = total_packet_size - cur_packet_size;
		ret = punit->video_write_packet(&vcfg, GET_PACKET_LEN, &nalstate, 0, &remain_size,
				load_buf->haddr, read_size);
		if (ret <= BLOCK) {
			if (!(buf = alloc_video_buf(punit)))
				goto audio;
			read_size = packet_read(punit, READ_PACKET, read_size, &pos, &punit->video_stream);
			if (read_size <= 0) { // the stream end
				ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
				punit->session_video = NULL;
				if (punit->session_audio != NULL) {
					ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
					punit->session_audio = NULL;
				}
				goto audio;
			}
			nalstate.total_len = total_packet_size - cur_packet_size;
			get_time_diff(punit);
			ret = punit->video_write_packet(&vcfg, GET_PACKET_DATA, &nalstate,
					buf->haddr, &buf->size, load_buf->haddr, read_size);
			push_video_buf(punit, buf);
		} else
			slice_flag = 1;
	} else
		slice_flag = 1;

	if (slice_flag) { // if packet data size is large than buffer length
		while (cur_packet_size < total_packet_size) {
			if (first_enter == 0) {
				read_preview_opt = PREVIEW_FIRST_SLICE;
				bsf_preview_opt = GET_FIRST_SLICE_LEN;
				read_data_opt = READ_FIRST_SLICE;
				bsf_data_opt = GET_FIRST_SLICE_DATA;
			} else {
				read_preview_opt = PREVIEW_OTHER_CLICE;
				bsf_preview_opt = GET_OTHER_CLICE_LEN;
				read_data_opt = READ_OTHER_CLICE;
				bsf_data_opt = GET_OTHER_CLICE_DATA;
			}

			read_size = packet_read(punit, read_preview_opt, 8, &pos, &punit->video_stream);
			if (read_size <= 0) { // the stream end
				ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
				punit->session_video = NULL;
				if (punit->session_audio != NULL) {
					ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
					punit->session_audio = NULL;
				}
				goto audio;
			}
			nalstate.total_len = total_packet_size - cur_packet_size;
			ret = punit->video_write_packet(&vcfg, bsf_preview_opt, &nalstate, 0, &remain_size,
					load_buf->haddr, total_packet_size - cur_packet_size);
			if (ret <= BLOCK) {
				if (!(buf = alloc_video_buf(punit)))
					goto audio;
				read_size = packet_read(punit, read_data_opt, nalstate.nallen + 4, &pos, &punit->video_stream);
				if (read_size <= 0) { // the stream end
					ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
					punit->session_video = NULL;
					if (punit->session_audio != NULL) {
						ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
						punit->session_audio = NULL;
					}
					goto audio;
				}
				nalstate.total_len = total_packet_size - cur_packet_size;
				ret = punit->video_write_packet(&vcfg, bsf_data_opt, &nalstate, buf->haddr, &buf->size,
						load_buf->haddr, read_size);
				cur_packet_size += read_size;
				if (read_data_opt == READ_FIRST_SLICE)
					get_time_diff(punit);
				push_video_buf(punit, buf);
			} else {
				if (!(buf = alloc_video_buf(punit)))
					goto audio;
				remain_size = nalstate.nallen + 4;
				read_size = packet_read(punit, read_data_opt, BLOCK - (ret - (nalstate.nallen + 4)),
										&pos, &punit->video_stream);
				if (read_size <= 0) { // the stream end
					ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
					punit->session_video = NULL;
					if (punit->session_audio != NULL) {
						ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
						punit->session_audio = NULL;
					}
					goto audio;
				}
				nalstate.total_len = total_packet_size - cur_packet_size;
				ret = punit->video_write_packet(&vcfg, bsf_data_opt, &nalstate, buf->haddr, &buf->size,
						load_buf->haddr, read_size);
				cur_packet_size += read_size;
				remain_size -= read_size;
				if (read_data_opt == READ_FIRST_SLICE)
					get_time_diff(punit);
				push_video_buf(punit, buf);
				while (remain_size > 0) {
					if (!(buf = alloc_video_buf(punit)))
						goto exit;
					if (remain_size < BLOCK)
						read_size = remain_size;
					else
						read_size = BLOCK;
					read_size = packet_read(punit, READ_CLICE_PURE, read_size, &pos, &punit->video_stream);
					if (read_size <= 0) { // the stream end
						ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
						punit->session_video = NULL;
						if (punit->session_audio != NULL) {
							ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
							punit->session_audio = NULL;
						}
						goto audio;
					}
					nalstate.total_len = total_packet_size - cur_packet_size;
					ret = punit->video_write_packet(&vcfg, GET_CLICE_PURE_DATA, &nalstate, buf->haddr, &buf->size,
							load_buf->haddr, read_size);
					remain_size -= read_size;
					cur_packet_size += read_size;
					push_video_buf(punit, buf);
				}
			}
			first_enter = 1;
		}
	}

	/* get audio packet */
audio:
	if (punit->session_audio != NULL) {
		if (punit->session_audio->state != SSTATE_RUNNING_IDLE) {
			goto exit;
		}
	} else
		goto exit;

	if (ac->cur_cluster_addr == 0)
		goto exit;

	punit->current_stream = &punit->audio_stream;
	read_size = packet_read(punit, READ_PACKET, 0, &pos, &punit->audio_stream);
#ifdef MEDIA_AV_SYNC
	punit->sync->a_cur_pkt_dts = ac->cur_cluster_time + ac->cur_block_time;
	//debug("\t\t\t\taudio dts time:%u\n", punit->sync->a_cur_pkt_dts);
#endif

	if (read_size <= 0) { // the stream end
		if (punit->session_video != NULL) {
			ret = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
			punit->session_video = NULL;
		}
		if (punit->session_audio != NULL) {
			ret = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
			punit->session_audio = NULL;
		}
		goto exit;
	}
	if (read_size + ADTS_HEADER_SIZE > BLOCK) {
		// TODO: if the audio packet is large than buffer, add codes
		debug("the audio packet is large than buffer, please add codes\n");
	}
	if (!(buf = alloc_audio_buf(punit)))
		goto exit;

	aacconfig_t acfg;
	acfg.sample_rate = punit->audio_stream.frequency;
	acfg.sample_size = punit->audio_stream.bit_depth;
	acfg.channel = punit->audio_stream.channels;
	ret = punit->audio_write_packet(&acfg, GET_PACKET_DATA, &nalstate,
			buf->haddr, &buf->size, load_buf->haddr, read_size);

	push_audio_buf(punit, buf);

exit:

	// free new buffer from buffer pop
	if (load_buf != punit->load_buf)
		sysbuf_free(load_buf);

	return load_buf->size;
}

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	long res = 0;
	int bstop = 0;

	SESSIONSTATE state;
	session_unit_t *punit = NULL;

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

#ifdef MEDIA_AV_SYNC
	media_flush_time(&punit->sync->sys_time, NULL);
#endif

	if(session->state != SSTATE_RUNNING
		&& session->state != SSTATE_RUNNING_IDLE)
		return session->state;

	res = SESSIONAPI(StreamLoad)(punit);

	if (punit->session_video != NULL) {
		state = punit->session_video->SessionRun(punit->session_video);
		if (punit->eof && state == SSTATE_STOP) {
			bstop |= 1;
		}
	}

audio:
	if (punit->session_audio != NULL) {
		state = punit->session_audio->SessionRun(punit->session_audio);
		if (punit->eof && state == SSTATE_STOP) {
			bstop |= 2;
		}
	}

exit:
	if ((bstop & 3) == 3) {
		debug("\tSession %s set to SSTATE_STOP\n", session->name);
		session->state = SSTATE_STOP;
	}
	if (!punit->session_audio && !punit->session_video)
		session->state = SSTATE_STOP;

	return session->state;
}

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	int res;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_INITED)
		return -EPERM;

	memset(punit, 0, sizeof(session_unit_t));

	if (file->filename) {
		if (f_open(&punit->file, file->filename, FA_READ) != FR_OK) {
			debug("open stream file error!\n");
			return -EPERM;
		} else {
			punit->load_size = f_size(&punit->file);
			debug("stream file size is %ld\n", punit->load_size);
		}
	}

	punit->readbuf = malloc(BUFFER_MAX);
	if (!punit->readbuf) {
		debug("mkv readbuf malloc fail!\n");
		return -ENOMEM;
	}
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	xthal_set_region_attribute((void *)(punit->readbuf), BUFFER_MAX, XCHAL_CA_BYPASS, 0);
#endif
	punit->audio_stream.bufaddr = punit->readbuf;
	punit->video_stream.bufaddr = punit->readbuf + AVDATA_MAX + 4;
	punit->audio_stream.channels = file->ta.channel;
	punit->audio_stream.frequency = file->ta.sample_rate;
	punit->audio_stream.bit_depth = file->ta.sample_size;
#ifdef MEDIA_AV_SYNC
	file->sync = &punit->avsync;
	punit->sync = file->sync;
	memset(punit->sync, 0, sizeof(session_av_sync));
#endif

	/* Probe mkv format type */
	if (mkv_probe(punit)) {
		debug("mkv file probe error!\n");
		res = -EBADF;
		goto exit;
	}

	/* Get stream info from format file */
	if (mkv_streaminfo_get(punit)) {
		debug("get stream info from mkv file error!\n");
		res = -EBADF;
		goto exit;
	}
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	file->tv.width = punit->video_stream.width;
	file->tv.height = punit->video_stream.height;
#endif

	/* Assignment function */
	if (file->tv.type == SST_H264) {
		punit->video_write_packet = media_bsf_h264toannexb;
	} else if (file->tv.type == SST_HEVC) {
		punit->video_write_packet = media_bsf_hevctoannexb;
	} else if (file->tv.type == SST_MPG2) {
		punit->video_write_packet = media_io_write_packet;
	} else if (file->tv.type == SST_MPG4) {
		punit->video_write_packet = media_mpeg4_to_m4v;
	} else {
		punit->video_write_packet = media_io_write_packet;
		debug("video type: %d not support currently\n", file->tv.type);
	}
	if (file->ta.type == SST_MP3) {
		punit->audio_write_packet = media_io_write_packet;
	} else if ((file->ta.type == SST_AAC) || (file->ta.type == SST_AAC_ADTS)) {
		punit->audio_write_packet = media_adts_aac_write_packet;
	} else {
		//TODO: add more type
		punit->audio_write_packet = media_io_write_packet;
		debug("audio type: %d not support currently\n", file->ta.type);
	}

#ifdef MEDIA_AV_SYNC
	/* Init param */
	punit->sync->v_last_play_timestamp.last_tick = xthal_get_ccount();
	punit->sync->a_play_buf_flush_timestamp.last_tick = xthal_get_ccount();
	punit->sync->sys_time.last_tick = xthal_get_ccount();
	punit->sync->a_real_rate = 44100; // default value, modify in i2sout module
	punit->sync->v_dts_time_base = 1000;
	punit->sync->a_dts_time_base = 1000;
	punit->sync->v_pkt_duration = (float)punit->video_stream.pkt_duration / 1000000;
	punit->sync->v_real_duration = punit->sync->v_pkt_duration;
	debug("video pakcet duration: %fms\n", punit->sync->v_pkt_duration);
	punit->sync->a_pkt_duration = (float)punit->audio_stream.pkt_duration / 1000000;
	punit->sync->v_last_decode_time = (float)punit->video_stream.pkt_duration / 1000000;
#endif

	file->ta.channel = punit->audio_stream.channels;
	file->ta.sample_rate = punit->audio_stream.frequency;
	file->ta.sample_size = punit->audio_stream.bit_depth;

	f_lseek(&punit->file, 0); // rewind to origin
	if (punit->load_buf)
		punit->load_buf->size = 0;

	session_file_t fl;
	punit->session_video = SessionGet(file->tv.type);
	if (punit->session_video != NULL) {
		fl.type = file->tv.type;
		fl.filename = NULL;
		fl.tv = file->tv;
#ifdef MEDIA_AV_SYNC
		fl.sync = &punit->avsync;
#endif
		fl.ftype = file->ftype;
		res = SessionCommand(punit->session_video, SSCMD_STREAM_START, &fl);
		if (res) {
			punit->session_video = NULL;
			goto exit;
		}

		punit->tv_valid_size = 0;
		res = SessionCommand(punit->session_video, SSCMD_STREAM_GETVALIDINSIZE, &punit->ta_valid_size);
		if (res) {
		}
	}

	punit->session_audio = SessionGet(file->ta.type);
	if (punit->session_audio != NULL) {
		fl.type = file->ta.type;
		fl.filename = NULL;
		fl.ta = file->ta;
#ifdef MEDIA_AV_SYNC
		fl.sync = &punit->avsync;
#endif
		fl.ftype = file->ftype;
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_START, &fl);
		if (res) {
			punit->session_audio = NULL;
			if (punit->session_video != NULL)
				res = SessionCommand(punit->session_video,SSCMD_STREAM_STOP, NULL);
			goto exit;
		}
#ifdef MEDIA_AV_SYNC
		punit->sync->v_real_duration = (punit->sync->v_pkt_duration * punit->sync->a_real_rate)
												/ file->ta.sample_rate;
#endif
		punit->ta_valid_size = 0;
		res = SessionCommand(punit->session_audio,SSCMD_STREAM_GETVALIDINSIZE, &punit->ta_valid_size);
		if (res) {
		}
	}

	SessionBufferDeInit(session);

	session->state = SSTATE_RUNNING;
	debug("\tSession %s set to SSTATE_RUNNING in SSCMD_STREAM_START\n", session->name);

	return 0;

exit:
	if (punit->readbuf) {
		free(punit->readbuf);
		punit->readbuf = NULL;
	}
	res = SessionCommand(punit->session, SSCMD_STREAM_STOP, NULL);
	return res;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	int res;

	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_RUNNING)
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;
	debug("\tSession %s set to SSTATE_STOP in SSCMD_STREAM_STOP\n", session->name);

	if (punit->readbuf) {
		free(punit->readbuf);
		punit->readbuf = NULL;
	}

	if (punit->load_buf)
		sysbuf_free(punit->load_buf);
	punit->load_buf = NULL;

	// close video
	if (punit->session_video != NULL) {
		res = SessionCommand(punit->session_video, SSCMD_STREAM_STOP, NULL);
		if (res) {
		}
		punit->session_video = NULL;
	}

	if (punit->loadbuf_video)
		sysbuf_free(punit->loadbuf_video);
	punit->loadbuf_video = NULL;

	// close audio
	if (punit->session_audio != NULL) {
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
		if (res) {
		}
		punit->session_audio = NULL;
	}

	if (punit->loadbuf_audio)
		sysbuf_free(punit->loadbuf_audio);
	punit->loadbuf_audio = NULL;

	//if (punit->file)
		f_close(&punit->file);

	SessionBufferDeInit(session);

	// set state
	session->state = SSTATE_INITED;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	if (psz) {
		*psz = 0;
	}

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (session == NULL || punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
		case SSCMD_STREAM_START:
		{
			debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
			return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
		}break;
		case SSCMD_STREAM_PAUSE:
		{
			debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
			if(session->state == SSTATE_RUNNING
				|| session->state == SSTATE_RUNNING_IDLE )
				session->state = SSTATE_RUNNING_PAUSE;
		}break;
		case SSCMD_STREAM_RESUME:
		{
			debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
			if(session->state == SSTATE_RUNNING_PAUSE)
				session->state = SSTATE_RUNNING;
		}break;
		case SSCMD_STREAM_STOP:
		{
			debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
			return SESSIONAPI(SSCMD_STREAM_STOP)(session);
		}break;
		case SSCMD_STREAM_BUFCHANGED:
		{
		}break;
		case SSCMD_STREAM_GETVALIDINSIZE:
		{
			return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t *)params);
		}break;
		default:
		{
			debug("Session '%s' command %d unknown !\n", session->name, cmd);
			ret = -EINVAL;
		};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	else
		return;

	debug("SessionDeInit : %s ..\n", session->name);

	SessionBufferDeInit(session);

	if (punit != NULL) {
		free(punit);
	}

	session->handle = NULL;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	session_unit_t *punit = (session_unit_t *)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_mkv =
{
	"mkv", (1 << SST_MKV), &SESSIONAPI(SessionInit)
};
